/* Convert numeric array to a structure
   Used to read structure from binary file

   Y = N2STRUCT(X, F, T) converts the UINT8 array X to a
   structure Y.  The field names are stored in the cell
   array F, and the type of data to be read for each field
   is stored in a corresponding cell array T.

   Each entry in the cell array T must be a valid FREAD
   precision format string ('char', 'int', 'double', etc).

   To read an array of consecutive values into a single
   structure field, the precision format string should
   include an integer M for an M vector, or two integer
   values M and N for an M x N array.  These values must
   follow the precision format, and should be separated
   by spaces.

   If a value in the UINT8 array X is not to be saved to
   the structure Y, provide an empty string as the field
   name.

   Ex:	X = [1 2 3 4 0 0 0 0 65 66 67]
		F = {'number', '', 'string'}
		T = {'uint8 4', 'uint32', 'char 3'}

		Y = n2struct(X, F, T)
			Y.number = [1 2 3 4]
			Y.string = 'ABC' */

/* By:   S.C. Molitor (smolitor@bme.jhu.edu)
   Date: May 12, 1999 */


// inclusions

#include <matrix.h>
#include <stdlib.h>
#include <string.h>
#include "mex.h"


// definitions
// min & max macros for portability

#define PRMAX	20		// maximum precision format string length
#define min(a, b)	((a < b) ? a : b)
#define max(a, b)	((a > b) ? a : b)


// function prototypes


// GETCELLELEMENTS - obtain strings from cell array

char **GetCellElements(const mxArray	*cell,		// pointer to cell array
					   int				ncell);		// number of cell elements

// DESTROY2DARRAY - deallocate space from 2D array

void Destroy2DArray(char	**array,	// pointer to array
					int		nrow);		// number of rows


// VALIDATEINPUTS - check for valid syntax

void ValidateInputs(int				nlhs, 
					int				nrhs,
					const mxArray	*prhs[]);


// MEXFUNCTION - gateway routine

void mexFunction(int			nlhs,
				 mxArray		*plhs[],
				 int			nrhs,
				 const mxArray	*prhs[]) {

	// declare variables

	char		**cellF;		// strings from cell array F
	char		**cellT;		// strings from cell array T
	char		**fname;		// field names in output structure Y
	char		fprec[PRMAX];	// field precision format strings
	char		*token;			// string tokens from cell array T
	char		*pcY;			// pointer to CHAR field of Y
	double		*prY;			// pointer to DOUBLE field of Y
	int			nfield;			// number of fields in structure Y
	int			nX, iX;			// number of elements & index of X
	int			ncell;			// number of elements in cell arrays F & T
	int			m, n;			// output structure Y field array dimensions
	int			nY;				// number of elements to copy from X to Y
	int			i, j;			// loop counters
	mxArray		*X, *F, *T;		// input array pointers
	mxArray		**Y;			// output structure Y field values
	uint8_T		*prX;			// pointer to data in input array X
	
	// call VALIDATEINPUTS to validate calling syntax

	ValidateInputs(nlhs, nrhs, prhs);

	// obtain pointers to input arrays
	
	X = (mxArray *) prhs[0];
	F = (mxArray *) prhs[1];
	T = (mxArray *) prhs[2];

	// obtain pointer to data in X
	// make sure X is UINT8

	if (mxGetClassID(X) == mxUINT8_CLASS) {
		prX = (uint8_T *) mxGetData(X);
		nX = mxGetNumberOfElements(X);
		if (nX <= 0) {
			mexErrMsgTxt("X cannot be empty");
		}
	}
	else {
		mexErrMsgTxt("Convert X to UINT8");
	}

	// obtain number of elements in cell arrays F & T
	// make sure each element is a string

	ncell = min(mxGetNumberOfElements(F), mxGetNumberOfElements(T));
	for (i = 0; i < ncell; i++) {
		if (!mxIsChar(mxGetCell(F, i))) {
			mexErrMsgTxt("Field name F must be cell array of strings");
		}
		if (!mxIsChar(mxGetCell(T, i))) {
			mexErrMsgTxt("Field type T must be cell array of strings");
		}
	}

	// obtain strings from cell arrays F & T

	cellF = GetCellElements(F, ncell);
	cellT = GetCellElements(T, ncell);

	// allocate storage for field names
	// allocate storage for field value arrays

	fname = (char **) mxMalloc((size_t) ncell * sizeof(char *));
	Y = (mxArray **) mxMalloc((size_t) ncell * sizeof(mxArray *));

	// parse strings to obtain precision formats & array dimensions

	for (i = 0, nfield = 0, iX = 0; (i < ncell) && (iX < nX); i++) {
		strcpy(fprec, "");
		token = strtok(cellT[i], " ,\t\n");
		for (j = 1; (j <= 3) && (token != NULL); j++) {
			switch (j) {
			case 1:
				memcpy(fprec, token, min(strlen(token) + 1, PRMAX));
				m = 1;
				n = 1;
				break;
			case 2:
				m = atoi(token);
				break;
			case 3:
				n = atoi(token);
				break;
			}
			token = strtok(NULL, " ,\t\n"); 
		}

		// skip data if field name is empty
		// make sure appropriate block size is skipped

		if (strcmp(cellF[i], "") == 0) {
			if ((strcmp(fprec, "char") == 0) || 
				(strcmp(fprec, "int8") == 0) || (strcmp(fprec, "uint8") == 0)) {
				iX += m * n;
			}
			else if ((strcmp(fprec, "int16") == 0) || (strcmp(fprec, "short") == 0)
				|| (strcmp(fprec, "uint16") == 0) || (strcmp(fprec, "ushort") == 0)) {
				iX += 2 * m * n;
			}
			else if ((strcmp(fprec, "int32") == 0) || (strcmp(fprec, "int") == 0)
				|| (strcmp(fprec, "uint32") == 0) || (strcmp(fprec, "uint") == 0)
				|| (strcmp(fprec, "float32") == 0) || (strcmp(fprec, "float") == 0)) {
				iX += 4 * m * n;
			}
			else if ((strcmp(fprec, "float64") == 0) || (strcmp(fprec, "double") == 0)) {
				iX += 8 * m * n;
			}
		}

		// otherwise obtain field names
		
		else {
			fname[nfield] = (char *) mxCalloc(strlen(cellF[i]) + 1, sizeof(char));
			strcpy(fname[nfield], cellF[i]);

			// determine size and type of value to be read from X
			// create pointers to field values
			// recast data from X and copy to CHAR/DOUBLE Y
			// increment pointer to X

			if (strcmp(fprec, "char") == 0) {
				pcY = (char *) mxCalloc((size_t) (m * n), sizeof(char));
				memcpy(pcY, &prX[iX], (size_t) (m * n)); 
				Y[nfield] = mxCreateString(pcY);
				iX += m * n;
				mxFree((void *) pcY);
			}
			else {
				Y[nfield] = mxCreateDoubleMatrix(m, n, mxREAL);
				prY = mxGetPr(Y[nfield]);
				nY = mxGetNumberOfElements(Y[nfield]);
				if (strcmp(fprec, "int8") == 0) {
					for (j = 0; j < nY; j++) {
						prY[j] = (double) *((char *) (&prX[iX]));
						iX++;
					}
				}
				else if (strcmp(fprec, "uint8") == 0) {
					for (j = 0; j < nY; j++) {
						prY[j] = (double) *((unsigned char *) (&prX[iX]));
						iX++;
					}
				}
				else if ((strcmp(fprec, "int16") == 0) || (strcmp(fprec, "short") == 0)) {
					for (j = 0; j < nY; j++) {
						prY[j] = (double) *((short *) (&prX[iX]));
						iX += 2;
					}
				}
				else if ((strcmp(fprec, "uint16") == 0) || (strcmp(fprec, "ushort") == 0)) {
					for (j = 0; j < nY; j++) {
						prY[j] = (double) *((unsigned short *) (&prX[iX]));
						iX += 2;
					}
				}
				else if ((strcmp(fprec, "int32") == 0) || (strcmp(fprec, "int") == 0)) {
					for (j = 0; j < nY; j++) {
						prY[j] = (double) *((int *) (&prX[iX]));
						iX += 4;
					}
				}
				else if ((strcmp(fprec, "uint32") == 0) || (strcmp(fprec, "uint") == 0)) {
					for (j = 0; j < nY; j++) {
						prY[j] = (double) *((unsigned int *) (&prX[iX]));
						iX += 4;
					}
				}
				else if ((strcmp(fprec, "float32") == 0) || (strcmp(fprec, "float") == 0)) {
					for (j = 0; j < nY; j++) {
						prY[j] = (double) *((float *) (&prX[iX]));
						iX += 4;
					}
				}
				else if ((strcmp(fprec, "float64") == 0) || (strcmp(fprec, "double") == 0)) {
					for (j = 0; j < nY; j++) {
						prY[j] = *((double *) (&prX[iX]));
						iX += 8;
					}
				}
				else {
					for (j = 0; j < nY; j++) {
						prY[j] = 0.0;
					}
				}
			}

			// increment field counter

			nfield++;
		}
	}

	// assign field values in output structure Y

	plhs[0] = mxCreateStructMatrix(1, 1, nfield, fname);
	for (i = 0; i < nfield; i++) {
		mxSetField(plhs[0], 0, fname[i], Y[i]);
	}

	// free allocated arrays

	Destroy2DArray(Y, 0);
	Destroy2DArray(fname, nfield);
	Destroy2DArray(cellT, ncell);
	Destroy2DArray(cellF, ncell);
}



// GETCELLELEMENTS - obtain strings from cell array

char **GetCellElements(const mxArray	*C,			// pointer to cell array
					   int				ncell) {	// number of cell elements

	// declare variables

	mxArray		*prC;			// pointers to cell array elements
	char		**array;		// strings from cell array 
	int			lenC;			// length of strings in cell array
	int			status;			// return value from mxGetString
	int			i;				// loop counter

	// allocate storage for strings in cell array
	// obtain strings from cell array

	array = (char **) mxMalloc((size_t) ncell * sizeof(char *));
	for (i = 0; i < ncell; i++) {
		prC = mxGetCell(C, i);
		lenC = mxGetNumberOfElements(prC) + 1;
		array[i] = (char *) mxCalloc(lenC, sizeof(char));
		status = mxGetString(prC, array[i], lenC);
	}
	return (array);
}


// DESTROY2DARRAY - deallocate space from 2D array

void Destroy2DArray(char	**array,	// pointer to array
					int		nrow) {		// number of rows

	// declare variables

	int		i;		// loop counter

	// deallocate space from 2D array

	for (i = 0; i < nrow; i++) {
		mxFree((void *) array[i]);
	}
	mxFree((void *) array);
}


// VALIDATEINPUTS - check for valid syntax

void ValidateInputs(int				nlhs, 
					int				nrhs,
					const mxArray	*prhs[]) {

	// check number of input arguments

	if (nrhs < 3) {
        mexErrMsgTxt("Not enough operands");
    }
	else if (nrhs > 3) {
		mexErrMsgTxt("Too many operands");
	}

	// check number of output arguments

	/*if (nlhs > 1) {
		mexErrMsgTxt("Too many output arguments");
	}*/
		
	// check input types

	if (!mxIsNumeric(prhs[0]) && !mxIsChar(prhs[0])) {
		mexErrMsgTxt("Input array must be numeric or character");
	}
    if (mxIsSparse(prhs[0])) {
        mexErrMsgTxt("Input array cannot be sparse");
    }
	if (!mxIsCell(prhs[1])) {
		mexErrMsgTxt("Field names must be a cell array");
	}
	if (!mxIsCell(prhs[2])) {
		mexErrMsgTxt("Field types must be a cell array");
	}
}
